module FileInfo;

import tango.io.Console;
import tango.io.digest.Crc32;
import tango.io.digest.Md2;
import tango.io.digest.Md4;
import tango.io.digest.Md5;
import tango.io.digest.Sha0;
import tango.io.digest.Sha1;
import tango.io.digest.Sha256;
import tango.io.digest.Sha512;
import tango.io.digest.Tiger;
import tango.io.digest.Digest;
import tango.io.Stdout;
import tango.io.FileScan;
import tango.io.FileConduit;
import tango.io.Print;
import tango.io.protocol.Reader;
import tango.text.convert.Layout;
import tango.text.stream.LineIterator;
import tango.io.Console;
import tango.text.convert.Integer;



import Version;

struct FileInfo{
	char[] filename;
	char[] checksum;
	long lastmod;
	bool checked;
	
	bool verify(long mod, char[] hash){
		return lastmod==mod && hash==checksum;
	}
	
	void update(long mod, char[] hash){
		lastmod=mod;
		checksum=hash.dup;
	}
}

class FileMgr{
	this(char[] ndirectory, char[] cyphername){
		directory=ndirectory.dup;
		digest=getDigest(cyphername);
		buildFileInfo;
	}
	
	Version* checkRepository(){		
		foreach(inout fileinfo;filesInfo){
			fileinfo.checked=false;
		}
		
		buf.clean();
		auto scan=new FileScan();
		scan(directory~"\\project",true);
		foreach(file; scan.files){
			FileInfo* finfo = isFileInRepository(file.toUtf8);
			auto mod=file.modified;
			auto fc= new FileConduit(file.toUtf8);
			auto content = new char[fc.length];
			auto bytesRead = fc.input.read (content);
			digest.update(content);
			auto hash = digest.hexDigest();
			
			if (finfo !is null){
				finfo.checked=true;
				if(!finfo.verify(mod, hash)){
					buf.addM(file.toUtf8);
					finfo.update(mod, hash);
				}
			} else {
				buf.addA(file.toUtf8);
				FileInfo v;
				v.filename=file.toUtf8.dup;
				v.checked=true;
				v.update(mod, hash);
				filesInfo~=v;
			}
		}
		
		for(int i=0; i<filesInfo.length; ++i){
			if(!filesInfo[i].checked){
				buf.addD(filesInfo[i].filename);
				filesInfo[i]=filesInfo[$-1];
				filesInfo.length=filesInfo.length-1;
			}
		}
		
		safeFileInfo();
		
		if(buf.isEmpty) {
			return null;
		} else {
			buf.writeVersion;
			return buf.clone;
		}
	}
	
	private:
	
	FileInfo* isFileInRepository(char[] name){
		foreach(inout fileinfo;filesInfo){
			if(fileinfo.filename==name)
				return &fileinfo;
		}
		
		return null;
	}
	
	void buildFileInfo(){
		auto fc = new FileConduit (directory~"\\info\\fileinfo.cfg", FileConduit.ReadExisting);
		int i=0;
		char[] filename;
		long mod;
		char[] hash;
		foreach (line; new LineIterator!(char) (fc)){
            if(i%3==0) filename=line.dup;
            if(i%3==1) hash=line.dup;
            if(i%3==2) {
            	mod=toLong(line);
            	FileInfo fi;
            	fi.filename=filename.dup;
            	fi.update(mod,hash);
            	filesInfo~=fi;
            }
            ++i;
		}
		fc.close();
	}
	
	void safeFileInfo(){
		 auto fc = new FileConduit (directory~"\\info\\fileinfo.cfg", FileConduit.WriteCreate);
		 auto writeshit = new Print!(char) (new Layout!(char),fc, null);
		 
		 foreach(fileinfo;filesInfo){
		 	/+writeshit(cast(char[])"File = {\n")();
		 	writeshit(cast(char[])"    filename   = '")(fileinfo.filename)(cast(char[])"'\n")();
		 	writeshit(cast(char[])"    checksum = '")(fileinfo.checksum)(cast(char[])"'\n")();
		 	writeshit(cast(char[])"    lastmod    =  ")(fileinfo.lastmod)(cast(char[])"\n")();
		 	writeshit(cast(char[])"}\n\n")();+/
		 	writeshit(fileinfo.filename)("\n")();
		 	writeshit(fileinfo.checksum)("\n")();
		 	writeshit(fileinfo.lastmod)("\n")();
		 	
		 }
		 fc.close();
	}
	
	Digest getDigest( char[] inDesiredCipher ){
		Digest d;
		switch( inDesiredCipher ){
			case "crc32":
				d = new Crc32;
				break;
				
			case "md2":
				d = new Md2;
				break;
				
			case "md4":
				d = new Md4;
				break;
				
			case "md5":
				d = new Md5;
				break;
				
			case "sha0":
				d = new Sha0;
				break;
				
			case "sha1":
				d = new Sha1;
				break;
				
			case "sha256":
				d = new Sha256;
				break;
				
			case "sha512":
				d = new Sha512;
				break;
				
			case "tiger":
				d = new Tiger;
				break;
				
			default :
				assert(false, "Unkwonw digest: "~inDesiredCipher);
				d=null;
				break;
		}

		return d;
	}

	
	FileInfo[] filesInfo;
	Digest digest;
	char[] directory;
	Version buf;
}